package com.tinyproxy.local;

import com.tinyproxy.common.Cfg;
import com.tinyproxy.common.Kits;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.socksx.SocksPortUnificationServerHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static java.util.concurrent.TimeUnit.SECONDS;

public class LocalServer extends Thread {

    final Cfg cfg;

    private static final String BANNER = """
             
             
             /__  ___/                     / /
               / /  ( )   __              / /         ___      ___      ___     //
              / /  / / //   ) ) //   / / / /        //   ) ) //   ) ) //   ) ) //
             / /  / / //   / / ((___/ / / /        //   / / //       //   / / //
            / /  / / //   / /      / / / /____/ / ((___/ / ((____   ((___( ( //
                            
            %s
            """;

    public LocalServer(Cfg cfg, Logger log) {
        this.cfg = cfg;
        log.info(BANNER.formatted(cfg));
    }

    @Override
    public void run() {
        final Logger log = LoggerFactory.getLogger(getClass());

        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup work = new NioEventLoopGroup(Kits.CPU_COUNT * 2);
        ServerBootstrap bootstrap = new ServerBootstrap().group(boss, work);
        try {
            bootstrap.channel(NioServerSocketChannel.class);
            bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) SECONDS.toMillis(10));
            bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {

                @Override
                protected void initChannel(SocketChannel ch) {
                    ch.pipeline().addLast(new SocksPortUnificationServerHandler());
                    ch.pipeline().addLast(new Socks45Handler(cfg));
                }
            });
            ChannelFuture future = bootstrap.bind(cfg.getLocalPort());

            future.sync();

            future.channel().closeFuture().sync();
        } catch (Exception e) {
            log.error("Failed to startup local tiny server\n\t[" + e + "]");
        } finally {
            boss.shutdownGracefully();
            work.shutdownGracefully();
        }

    }

}
